/**********************************************************************************************************************
 *
 * Project:    mGameEngine
 * File:       matrix2x2.h
 * Author:     karooolek
 * Created on: 2009-02-12
 *
 **********************************************************************************************************************/

#ifndef MATRIX2X2_H_
#define MATRIX2X2_H_

#include "vector2.h"
#include <cstring>
#include <algorithm>

namespace mGameEngine
{

/**
 * Two by two floats matrix.
 */
struct Matrix2x2
{
    /**
     * Matrix components
     */
    union
    {
        float m[2][2];
        float _m[4];
    };

    /**
     * Create matrix with initial value a on diagonal.
     * @param a diagonal value
     */
    Matrix2x2(float a = 1.0f)
    {
        _m[0] = a;    _m[2] = 0.0f;
        _m[1] = 0.0f; _m[3] = a;
    }

    /**
     * Create matrix with initial values.
     * @param m00 first column, first row
     * @param m10 second column, first row
     * @param m01 first column, second row
     * @param m11 second column, second row
     */
    Matrix2x2(float m00, float m10,
              float m01, float m11)
    {
        _m[0] = m00; _m[2] = m10;
        _m[1] = m01; _m[3] = m11;
    }

    /**
     * Copy constructor.
     * @param m2 matrix to copy.
     */
    Matrix2x2(const Matrix2x2 &m2)
    {
        operator=(m2);
    }

    /**
     * Create matrix with initial values from two vectors.
     * @param v0 first column
     * @param v1 second column
     */
    Matrix2x2(const Vector2 &v0,
              const Vector2 &v1)
    {
        _m[0] = v0.x; _m[2] = v1.x;
        _m[1] = v0.y; _m[3] = v1.y;
    }

    /**
     * Create matrix with initial values from values array.
     * @param v four floats array
     */
    Matrix2x2(const float *v)
    {
        memcpy(_m, v, 4 * sizeof(float));
    }

    /**
     * Assign matrix.
     * @param m2 matrix to assign.
     * @return matrix with assigned value.
     */
    Matrix2x2 &operator=(const Matrix2x2 &m2)
    {
        memcpy(_m, m2._m, 4 * sizeof(float));
        return *this;
    }

    /**
     * Add matrix.
     * @param m2 matrix to add
     * @return sum of matrices
     */
    Matrix2x2 &operator+=(const Matrix2x2 &m2)
    {
        _m[0] += m2._m[0]; _m[2] += m2._m[2];
        _m[1] += m2._m[1]; _m[3] += m2._m[3];
        return *this;
    }

    /**
     * Add matrices.
     * @param m2 matrix to add
     * @return sum of matrices
     */
    Matrix2x2 operator+(const Matrix2x2 &m2) const
    {
        return Matrix2x2(_m[0] + m2._m[0], _m[2] + m2._m[2],
                         _m[1] + m2._m[1], _m[3] + m2._m[3]);
    }

    /**
     * Negate matrix.
     * @return negated matrix
     */
    Matrix2x2 operator-() const
    {
        return *this * -1.0f;
    }

    /**
     * Subtract matrix.
     * @param m2 matrix to subtract
     * @return difference of matrices
     */
    Matrix2x2 &operator-=(const Matrix2x2 &m2)
    {
        _m[0] -= m2._m[0];    _m[2] -= m2._m[2];
        _m[1] -= m2._m[1];    _m[3] -= m2._m[3];
        return *this;
    }

    /**
     * Subtract matrices.
     * @param m2 matrix to subtract
     * @return difference of matrices
     */
    Matrix2x2 operator-(const Matrix2x2 &m2) const
    {
        return Matrix2x2(_m[0] - m2._m[0],    _m[2] - m2._m[2],
                         _m[1] - m2._m[1],    _m[3] - m2._m[3]);
    }

    /**
     * Multiply matrix.
     * @param m2 matrix multilplier
     * @return multiplied matrix
     */
    Matrix2x2 &operator*=(const Matrix2x2 &m2)
    {
        float m00 = _m[0];
        float m01 = _m[1];

        _m[0] = m00 * m2._m[0] + _m[2] * m2._m[1];
        _m[2] = m00 * m2._m[2] + _m[2] * m2._m[3];

        _m[1] = m01 * m2._m[0] + _m[3] * m2._m[1];
        _m[3] = m01 * m2._m[2] + _m[3] * m2._m[3];

        return *this;
    }

    /**
     * Multiply matrices.
     * @param m2 matrix multiplier
     * @return multiplied matrices
     */
    Matrix2x2 operator*(const Matrix2x2 &m2) const
    {
        return Matrix2x2(_m[0] * m2._m[0] + _m[2] * m2._m[1],
                         _m[0] * m2._m[2] + _m[2] * m2._m[3],

                         _m[1] * m2._m[0] + _m[3] * m2._m[1],
                         _m[1] * m2._m[2] + _m[3] * m2._m[3]);
    }

    /**
     * Multiply by vector.
     * @param v vector multiplier
     * @return multiplied matrix - a vector
     */
    Vector2 operator*(const Vector2 &v) const
    {
        return Vector2(_m[0] * v.x + _m[2] * v.y,
                       _m[1] * v.x + _m[3] * v.y);
    }

    /**
     * Multiply by scalar.
     * @param s scalar mutliplier
     * @return multiplied matrix
     */
    Matrix2x2 &operator*=(float s)
    {
        _m[0] *= s;    _m[2] *= s;
        _m[1] *= s;    _m[3] *= s;

        return *this;
    }

    /**
     * Multiply by scalar.
     * @param s scalar multiplier
     * @return multiplied matrix
     */
    Matrix2x2 operator*(float s) const
    {
        return Matrix2x2(_m[0] * s, _m[2] * s,
                         _m[1] * s, _m[3] * s);

    }

    /**
     * Divide by scalar.
     * @param s scalar divisor
     * @return divided matrix
     */
    Matrix2x2 &operator/=(float s)
    {
        s = s ? 1.0f/s : 1.0f;

        _m[0] *= s;    _m[2] *= s;
        _m[1] *= s;    _m[3] *= s;

        return *this;
    }

    /**
     * Divide by scalar.
     * @param s scalar divisor
     * @return divided matrix
     */
    Matrix2x2 operator/(float s) const
    {
        s = s ? 1.0f/s : 1.0f;

        return Matrix2x2(_m[0] * s, _m[2] * s,
                         _m[1] * s, _m[3] * s);
    }

    /**
     * Matrices equal.
     * @param m2 matrix to compare
     * @return true if all components equal
     */
    bool operator==(const Matrix2x2 &m2) const
    {
        return
        (
            _m[0] == m2._m[0] && _m[2] == m2._m[2] &&
            _m[1] == m2._m[1] && _m[3] == m2._m[3]
        );
    }

    /**
     * Matrices differ.
     * @param m2 matrix to compare
     * @return true if any of components differs
     */
    bool operator!=(const Matrix2x2 &m2) const
    {
        return
        (
            _m[0] != m2._m[0] || _m[2] != m2._m[2] ||
            _m[1] != m2._m[1] || _m[3] != m2._m[3]
        );
    }

    /**
     * Floats array cast.
     * @return floats array
     */
    operator float *()
    {
        return _m;
    }

    /**
     * Floats const array cast.
     * @return floats const array
     */
    operator const float *() const
    {
        return _m;
    }

    /**
     * Transpose matrix.
     * @return transposed matrix.
     */
    Matrix2x2 &transpose()
    {
        std::swap(_m[1], _m[2]);

        return *this;
    }

    /**
     * Invert matrix.
     * @return inverted matrix.
     */
    Matrix2x2 &invert()
    {
        // calcualte determinant
        float det = 1.0f / (_m[0] * _m[3] - m[0][1] * _m[2]);

        // invert matrix
        float tmp = _m[0];
        _m[0] = _m[3];    _m[2] = -_m[2];
        _m[1] = -_m[1];    _m[3] = tmp;
        *this *= det;

        return *this;
    }

    /**
     * Spherical linear interpolation between two matrices.
     * @param m1 first matrix
     * @param m2 second matrix
     * @param t interpolation factor.
     * @return spherically linearly interpolated matrix between m1 and m2.
     */
    friend Matrix2x2 slerp(const Matrix2x2 &m1, const Matrix2x2 &m2, float t)
    {
        return Matrix2x2(slerp(Vector2(m1.m[0]), Vector2(m2.m[0]), t),
                         slerp(Vector2(m1.m[1]), Vector2(m2.m[1]), t));
    }
};

}

#endif // MATRIX2X2_H_
